AUI.add('aui-color-picker', function(A) {
/**
 * The ColorPicker Utility - Full documentation coming soon.
 *
 * @module aui-color-picker
 */

var Lang = A.Lang,
	isArray = Lang.isArray,
	isObject = Lang.isObject,

	NAME = 'colorpicker',

	getClassName = A.ClassNameManager.getClassName,

	WidgetStdMod = A.WidgetStdMod,

	CSS_CANVAS = getClassName(NAME, 'canvas'),
	CSS_HUE_CANVAS = getClassName(NAME, 'hue-canvas'),
	CSS_CONTAINER = getClassName(NAME, 'container'),
	CSS_CONTROLS_CONTAINER = getClassName(NAME, 'controls'),

	CSS_PANEL = getClassName(NAME, 'panel'),
	CSS_SWATCH_CONTAINER = getClassName(NAME, 'swatch'),
	CSS_SWATCH_CURRENT = getClassName(NAME, 'swatch-current'),
	CSS_SWATCH_ORIGINAL = getClassName(NAME, 'swatch-original'),
	CSS_THUMB_CANVAS = getClassName(NAME, 'thumb'),
	CSS_THUMB_CANVAS_IMAGE = getClassName(NAME, 'thumb-image'),
	CSS_HUE_THUMB = getClassName(NAME, 'hue-thumb'),
	CSS_HUE_THUMB_IMAGE = getClassName(NAME, 'hue-thumb-image'),
	CSS_TRIGGER= getClassName(NAME, 'trigger'),

	TPL_CANVAS = '<div class="' + CSS_CANVAS + '"></div>',
	TPL_HUE_CANVAS = '<span class="' + CSS_HUE_CANVAS + '"></span>',
	TPL_SWATCH_CONTAINER = '<div class="' + CSS_SWATCH_CONTAINER + '"></div>',
	TPL_SWATCH_CURRENT = '<div class="' + CSS_SWATCH_CURRENT + '"></div>',
	TPL_SWATCH_ORIGINAL = '<div class="' + CSS_SWATCH_ORIGINAL + '"></div>',
	TPL_THUMB_CANVAS = '<div class="' + CSS_THUMB_CANVAS + '"><div class="' + CSS_THUMB_CANVAS_IMAGE + '"></div></div>',
	TPL_THUMB_HUE = '<span class="' + CSS_HUE_THUMB + '"><span class="' + CSS_HUE_THUMB_IMAGE + '"></span></span>';

var Color = {
	real2dec: function(number) {
		var instance = this;

		return Math.min(255, Math.round(number * 256));
	},

	hsv2rgb: function(hue, saturation, value) {
		var instance = this;

		if (isArray(hue)) {
			return instance.hsv2rgb.apply(instance, hue);
		}
		else if (isObject(hue)) {
			saturation = hue.saturation;
			value = hue.value;
			hue = hue.hue;
		}

		hue = instance.constrainTo(hue, 0, 360, 0);
		value = instance.constrainTo(value, 0, 1, 0);
		saturation = instance.constrainTo(saturation, 0, 1, 0);

		var red,
			green,
			blue,
			i = Math.floor((hue / 60) % 6),
			f = (hue / 60) - i,
			p = value * (1 - saturation),
			q = value * (1 - f * saturation),
			t = value * (1 - (1 - f) * saturation);

		switch (i) {
			case 0:
				red = value;
				green = t;
				blue = p;
			break;
			case 1:
				red = q;
				green = value;
				blue = p;
			break;
			case 2:
				red = p;
				green = value;
				blue = t;
			break;
			case 3:
				red = p;
				green = q;
				blue = value;
			break;
			case 4:
				red = t;
				green = p;
				blue = value;
			break;
			case 5:
				red = value;
				green = p;
				blue = q;
			break;
		}

		var real2dec = instance.real2dec;

		return {
			red: real2dec(red),
			green: real2dec(green),
			blue: real2dec(blue)
		};
	},

	rgb2hex: function(red, green, blue) {
		var instance = this;

		if (isArray(red)) {
			return instance.rgb2hex.apply(instance, red);
		}
		else if (isObject(red)) {
			green = red.green;
			blue = red.blue;
			red = red.red;
		}

		var dec2hex = instance.dec2hex;

		return dec2hex(red) + dec2hex(green) + dec2hex(blue);
	},

	dec2hex: function(number) {
		var instance = this;

		number = parseInt(number, 10)|0;
		number = Color.constrainTo(number, 0, 255, 0);

		return ('0' + number.toString(16)).slice(-2).toUpperCase();
	},

	hex2dec: function(string) {
		var instance = this;

		return parseInt(string, 16);
	},

	hex2rgb: function(string) {
		var instance = this;

		var hex2dec = instance.hex2dec;

		var red = string.slice(0, 2);
		var green = string.slice(2, 4);
		var blue = string.slice(4, 6);

		return {
			red: hex2dec(red),
			green: hex2dec(green),
			blue: hex2dec(blue)
		};
	},

	rgb2hsv: function(red, blue, green) {
		var instance = this;

		if (isArray(red)) {
			return instance.rgb2hsv.apply(instance, red);
		}
		else if (isObject(red)) {
			green = red.green;
			blue = red.blue;
			red = red.red;
		}

		red /= 255;
		green /= 255;
		blue /= 255;

		var hue,
			saturation,
			min = Math.min(Math.min(red, green), blue),
			max = Math.max(Math.max(red, green), blue),
			delta = max - min;

		switch (max) {
			case min:
				hue = 0;
			break;
			case red:
				hue = 60 * (green - blue) / delta;
				if (green < blue) {
					hue += 360;
				}
			break;
			case green:
				hue = (60 * (blue - red) / delta) + 120;
			break;
			case blue:
				hue = (60 * (red - green) / delta) + 240;
			break;
		}

		saturation = (max === 0) ? 0 : 1- (min / max);

		return {
			hue: Math.round(hue),
			saturation: saturation,
			value: max
		};
	},

	constrainTo: function(number, start, end, defaultNumber) {
		var instance = this;

		if (number < start || number > end) {
			number = defaultNumber;
		}

		return number;
	}
};

/**
 * A base class for ColorPicker, providing:
 * <ul>
 *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 *    <li>ColorPicker utility</li>
 * </ul>
 *
 * Check the list of <a href="ColorPicker.html#configattributes">Configuration Attributes</a> available for
 * ColorPicker.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class ColorPicker
 * @constructor
 * @extends OverlayContext
 */
var ColorPicker = A.Component.create(
	{
		/**
		 * Static property provides a string to identify the class.
		 *
		 * @property ColorPicker.NAME
		 * @type String
		 * @static
		 */
		NAME: NAME,
		/**
		 * Static property used to define the default attribute
		 * configuration for the ColorPicker.
		 *
		 * @property ColorPicker.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			colors: {
				value: {},
				getter: function() {
					var instance = this;

					var rgb = instance.get('rgb');
					var hex = instance.get('hex');

					var colors = {};

					A.mix(colors, rgb);

					colors.hex = hex;

					return colors;
				}
			},
			hex: {
				value: 'FFFFFF',
				getter: function() {
					var instance = this;

					var rgb = instance.get('rgb');

					return Color.rgb2hex(rgb);
				},
				setter: function(value) {
					var instance = this;

					var length = value.length;

					if (length == 3) {
						var chars = value.split('');

						for (var i = 0; i < chars.length; i++) {
							chars[i] += chars[i];
						}

						value = chars.join('');
					}

					if ((/[A-Fa-f0-9]{6}/).test(value)) {
						var rgb = Color.hex2rgb(value);

						instance.set('rgb', rgb);
					}
					else {
						value = A.Attribute.INVALID_VALUE;
					}

					return value;
				}
			},

			hideOn: {
				value: 'click'
			},

			hsv: {
				getter: function(value) {
					var instance = this;

					var rgb = instance.get('rgb');

					return Color.rgb2hsv(rgb);
				},
				setter: function(value) {
					var instance = this;

					if (isArray(value)) {
						var current = instance.get('hsv');

						var rgb = Color.hsv2rgb(value);

						instance.set('rgb', rgb);

						current = {
							hue: value[0],
							saturation: value[1],
							value: [2]
						};
					}
					else if (!isObject(value)) {
						value = A.Attribute.INVALID_VALUE;
					}

					return value;
				},
				value: {
					hue: 0,
					saturation: 0,
					value: 0
				}
			},

			showOn: {
				value: 'click'
			},

			pickersize: {
				value: 180
			},

			rgb: {
				value: {
					blue: 255,
					green: 255,
					red: 255
				},

				setter: function(value) {
					var instance = this;

					if (isArray(value)) {
						var current = instance.get('rgb');

						current = {
							blue: value[2],
							green: value[1],
							red: [0]
						};

						value = current;
					}
					else if (!isObject(value)) {
						value = A.Attribute.INVALID_VALUE;
					}

					value.red = Color.constrainTo(value.red, 0, 255, 255);
					value.green = Color.constrainTo(value.green, 0, 255, 255);
					value.blue = Color.constrainTo(value.blue, 0, 255, 255);

					return value;
				}
			},

			strings: {
				value: {
					R: 'R',
					G: 'G',
					B: 'B',
					H: 'H',
					S: 'S',
					V: 'V',
					HEX: '#',
					DEG: '\u00B0',
					PERCENT: '%'
				}
			},

			triggerParent: {
				value: null
			},

			trigger: {
				lazyAdd: true,
				getter: function(value) {
					var instance = this;

					if (!value) {
						instance._buttonTrigger = new A.ButtonItem(
							{
								cssClass: CSS_TRIGGER,
								icon: 'pencil'
							}
						);

						value = instance._buttonTrigger.get('boundingBox');

						instance.set('trigger', value);
					}

					return value;
				}
			}
		},

		EXTENDS: A.OverlayContext,

		prototype: {
			/**
			 * Create the DOM structure for the ColorPicker. Lifecycle.
			 *
			 * @method renderUI
			 * @protected
			 */
			renderUI: function() {
				var instance = this;

				var toolTrigger = instance._buttonTrigger;

				if (toolTrigger && !toolTrigger.get('rendered')) {
					var triggerParent = instance.get('triggerParent');

					if (!triggerParent) {
						triggerParent = instance.get('boundingBox').get('parentNode');
					}

					toolTrigger.render(triggerParent);
				}

				instance._renderContainer();
				instance._renderSliders();
				instance._renderControls();
			},

			/**
			 * Bind the events on the ColorPicker UI. Lifecycle.
			 *
			 * @method bindUI
			 * @protected
			 */
			bindUI: function() {
				var instance = this;

				ColorPicker.superclass.bindUI.apply(this, arguments);

				instance._createEvents();

				instance._colorCanvas.on('mousedown', instance._onCanvasMouseDown, instance);

				instance._colorPicker.on('drag:start', instance._onThumbDragStart, instance);

				instance._colorPicker.after('drag:drag', instance._afterThumbDrag, instance);
				instance._hueSlider.after('valueChange', instance._afterValueChange, instance);

				var formNode = instance._colorForm.get('contentBox');

				formNode.delegate('change', A.bind(instance._onFormChange, instance), 'input');

				instance.after('hexChange', instance._updateRGB);
				instance.after('rgbChange', instance._updateRGB);

				instance._colorSwatchOriginal.on('click', instance._restoreRGB, instance);

				instance.after('visibleChange', instance._afterVisibleChangeCP);
			},

			/**
			 * Sync the ColorPicker UI. Lifecycle.
			 *
			 * @method syncUI
			 * @protected
			 */
			syncUI: function() {
				var instance = this;

				instance._updatePickerOffset();

				var rgb = instance.get('rgb');

				instance._updateControls();

				instance._updateOriginalRGB();
			},

			_afterThumbDrag: function(event) {
				var instance = this;

				var value = instance._translateOffset(event.pageX, event.pageY);

				if (!instance._preventDragEvent) {
					instance.fire(
						'colorChange',
						{
							ddEvent: event
						}
					);
				}

				instance._canvasThumbXY = value;
			},

			_afterValueChange: function(event) {
				var instance = this;

				if (event.src != 'controls') {
					instance.fire(
						'colorChange',
						{
							slideEvent: event
						}
					);
				}
			},

			_afterVisibleChangeCP: function(event) {
				var instance = this;

				if (event.newVal) {
					instance.refreshAlign();
					instance._hueSlider.syncUI();
				}

				instance._updateOriginalRGB();
			},

			_convertOffsetToValue: function(x, y) {
				var instance = this;

				if (isArray(x)) {
					return instance._convertOffsetToValue.apply(instance, x);
				}

				var size = instance.get('pickersize');

				x = Math.round(((x * size / 100)));
				y = Math.round((size - (y * size / 100)));

				return [x, y];
			},

			_convertValueToOffset: function(x, y) {
				var instance = this;

				if (isArray(x)) {
					return instance._convertValueToOffset.apply(instance, x);
				}

				x = Math.round(x + instance._offsetXY[0]);
				y = Math.round(y + instance._offsetXY[1]);

				return [x, y];
			},

			_createEvents: function() {
				var instance = this;

				instance.publish(
					'colorChange',
					{
						defaultFn: instance._onColorChange
					}
				);
			},

			_getHuePicker: function() {
				var instance = this;

				var size = instance._getPickerSize();
				var hue = (size - instance._hueSlider.get('value')) / size;

				hue = Math.round(hue * 360);

				return (hue === 360) ? 0 : hue;
			},

			_getPickerSize: function() {
				var instance = this;

				if (!instance._pickerSize) {
					var colorCanvas = instance._colorCanvas;
					var pickerSize = colorCanvas.get('offsetWidth');

					if (!pickerSize) {
						pickerSize = colorCanvas.getComputedStyle('width');
					}

					pickerSize = parseInt(pickerSize, 10);

					var width = instance._pickerThumb.get('offsetWidth');

					pickerSize -= width;

					instance._pickerSize = pickerSize;
				}

				return instance._pickerSize;
			},

			_getSaturationPicker: function() {
				var instance = this;

				return instance._canvasThumbXY[0] / instance._getPickerSize();
			},

			_getThumbOffset: function() {
				var instance = this;

				if (!instance._thumbOffset) {
					var pickerThumb = instance._pickerThumb;

					var height = pickerThumb.get('offsetHeight');
					var width = pickerThumb.get('offsetWidth');

					instance._thumbOffset = [Math.floor(width / 2), Math.floor(height / 2)];
				}

				return instance._thumbOffset;
			},

			_getValuePicker: function() {
				var instance = this;

				var size = instance._getPickerSize();

				return ((size - instance._canvasThumbXY[1])) / size;
			},

			_onCanvasMouseDown: function(event) {
				var instance = this;

				instance._setDragStart(event.pageX, event.pageY);

				event.halt();

				instance.fire(
					'colorChange',
					{
						ddEvent: event
					}
				);
			},

			_onColorChange: function(event) {
				var instance = this;

				var hue = instance._getHuePicker();

				var saturation = instance._getSaturationPicker();
				var value = instance._getValuePicker();

				var rgb = Color.hsv2rgb(hue, saturation, value);

				if (event.src != 'controls') {
					instance.set('rgb', rgb);
				}

				instance._updateControls();

				if (!event.ddEvent) {
					if (!event.slideEvent) {
						instance._updateHue();
						instance._updatePickerThumb();

						hue = instance._getHuePicker();
					}

					var canvasRGB = Color.hsv2rgb(hue, 1, 1);

					instance._updateCanvas(canvasRGB);
				}

				instance._updateColorSwatch();
			},

			_onFormChange: function(event) {
				var instance = this;

				var input = event.currentTarget;

				var colorKey = input.get('id');

				if (colorKey != 'hex') {
					colorKey = 'rgb.' + colorKey;
				}

				instance.set(colorKey, input.val());
			},

			_onThumbDragStart: function(event) {
				var instance = this;

				instance._updatePickerOffset();
			},

			_renderContainer: function() {
				var instance = this;

				if (!instance._pickerContainer) {
					var container = new A.Panel(
						{
							cssClass: CSS_PANEL,
							icons: [
								{
									icon: 'close',
									id: 'close',
									handler: {
										fn: instance.hide,
										context: instance
									}
								}
							]
						}
					)
					.render(instance.get('contentBox'));

					var bodyNode = container.bodyNode;

					bodyNode.addClass(CSS_CONTAINER);

					instance._pickerContainer = bodyNode;
				}
			},

			_renderControls: function() {
				var instance = this;

				instance._colorSwatch = A.Node.create(TPL_SWATCH_CONTAINER);
				instance._colorSwatchCurrent = A.Node.create(TPL_SWATCH_CURRENT);
				instance._colorSwatchOriginal = A.Node.create(TPL_SWATCH_ORIGINAL);

				instance._colorSwatch.appendChild(instance._colorSwatchCurrent);
				instance._colorSwatch.appendChild(instance._colorSwatchOriginal);

				instance._pickerContainer.appendChild(instance._colorSwatch);

				var strings = instance.get('strings');

				var form = new A.Form(
					{
						labelAlign: 'left'
					}
				).render(instance._pickerContainer);

				form.add(
					[
						{
							id: 'red',
							labelText: strings.R,
							size: 3
						},
						{
							id: 'green',
							labelText: strings.G,
							size: 3
						},
						{
							id: 'blue',
							labelText: strings.B,
							size: 3
						},
						{
							id: 'hex',
							labelText: strings.HEX,
							size: 6
						}
					],
					true
				);

				form.get('boundingBox').addClass(CSS_CONTROLS_CONTAINER);

				instance._colorForm = form;
			},

			_renderSliders: function() {
				var instance = this;

				instance._colorCanvas = A.Node.create(TPL_CANVAS);
				instance._pickerThumb = A.Node.create(TPL_THUMB_CANVAS);

				instance._colorCanvas.appendChild(instance._pickerThumb);

				instance._pickerContainer.appendChild(instance._colorCanvas);

				var size = instance.get('pickersize');

				instance._colorPicker = new A.DD.Drag(
					{
						node: instance._pickerThumb
					}
				)
				.plug(
					A.Plugin.DDConstrained,
					{
						constrain2node: instance._colorCanvas
					}
				);

				instance._hueSlider = new A.Slider(
					{
						axis: 'y',
						min: 0,
						max: size,
						length: instance._colorCanvas.get('offsetHeight')
					}
				);

				instance._hueSlider.RAIL_TEMPLATE = TPL_HUE_CANVAS;
				instance._hueSlider.THUMB_TEMPLATE = TPL_THUMB_HUE;

				instance._hueSlider.render(instance._pickerContainer);
			},

			_restoreRGB: function(event) {
				var instance = this;

				instance.set('rgb', instance._oldRGB);
				instance._updateHue();
				instance._updatePickerThumb();
				instance._updateColorSwatch();

				instance.fire('colorChange');
			},

			_setDragStart: function(x, y) {
				var instance = this;

				if (isArray(x)) {
					return instance._setDragStart.apply(instance, x);
				}

				var dd = instance._colorPicker;

				dd._dragThreshMet = true;
				dd._fixIEMouseDown();

				A.DD.DDM.activeDrag = dd;

				var xy = dd.get('dragNode').getXY();

				var thumbOffset = instance._getThumbOffset();

				xy[0] += thumbOffset[0];
				xy[1] += thumbOffset[1];

				dd._setStartPosition(xy);
				dd.set('activeHandle', dd.get('dragNode'));

				dd.start();
				dd._alignNode([x, y]);
			},

			_translateOffset: function(x, y) {
				var instance = this;

				var offsetXY = instance._offsetXY;
				var offset = [];

				offset[0] = Math.round(x - offsetXY[0]);
				offset[1] = Math.round(y - offsetXY[1]);

				return offset;
			},

			_updateCanvas: function(rgb) {
				var instance = this;

				rgb = rgb || instance.get('rgb');

				instance._colorCanvas.setStyle('backgroundColor', 'rgb(' + [rgb.red, rgb.green, rgb.blue].join(',') + ')');
			},

			_updateColorSwatch: function(rgb) {
				var instance = this;

				rgb = rgb || instance.get('rgb');

				instance._colorSwatchCurrent.setStyle('backgroundColor', 'rgb(' + [rgb.red, rgb.green, rgb.blue].join(',') + ')');
			},

			_updateControls: function() {
				var instance = this;

				var colors = instance.get('colors');

				instance._colorForm.set('values', colors);
			},

			_updateHue: function() {
				var instance = this;

				var size = instance._getPickerSize();
				var hue = instance.get('hsv.hue');

				hue = size - Math.round(hue / 360 * size);

				if (hue === size) {
					hue = 0;
				}

				instance._hueSlider.set(
					'value',
					 hue,
					{
						src: 'controls'
					}
				);
			},

			_updateOriginalColorSwatch: function(rgb) {
				var instance = this;

				rgb = rgb || instance.get('rgb');

				instance._colorSwatchOriginal.setStyle('backgroundColor', 'rgb(' + [rgb.red, rgb.green, rgb.blue].join(',') + ')');
			},

			_updateOriginalRGB: function() {
				var instance = this;

				instance._oldRGB = instance.get('rgb');
				instance._updateOriginalColorSwatch(instance._oldRGB);
			},

			_updatePickerOffset: function() {
				var instance = this;

				instance._offsetXY = instance._colorCanvas.getXY();
			},

			_updatePickerThumb: function() {
				var instance = this;

				instance._updatePickerOffset();

				var hsv = instance.get('hsv');

				var size = instance.get('pickersize');

				hsv.saturation = Math.round(hsv.saturation * 100);
				var saturation = hsv.saturation;

				hsv.value = Math.round(hsv.value * 100);
				var value = hsv.value;

				var xy = instance._convertOffsetToValue(saturation, value);

				xy = instance._convertValueToOffset(xy);

				instance._canvasThumbXY = xy;

				var dd = instance._colorPicker;

				instance._preventDragEvent = true;

				dd._setStartPosition(dd.get('dragNode').getXY());
	            dd._alignNode(xy, true);

				instance._preventDragEvent = false;
			},

			_updateRGB: function(event) {
				var instance = this;

				if (event.subAttrName || event.attrName == 'hex') {
					instance.fire(
						'colorChange',
						{
							src: 'controls'
						}
					);
				}
			},

			_canvasThumbXY: [0, 0],
			_offsetXY: [0, 0]
		}
	}
);

ColorPicker.Color = Color;
A.ColorPicker = ColorPicker;

}, '1.0.1' ,{skinnable:true, requires:['aui-overlay-context','dd-drag','slider','substitute','aui-button-item','aui-form-base','aui-panel']});
